// main.cpp : Defines the entry point for the console application.

#include "stdafx.h"
#undef UNICODE
#include <Windows.h>
#include <iostream>
#include <stdio.h>
#include <stdlib.h>
#include <string>
#include <mcam_zei.h>
#include <tiff.h>
#include <tiffio.h>
#include <tiffconf.h>
#include <tif_config.h>
#include <opencv2\core\core.hpp>
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <MMCore.h> //micro-manager core
#include <Configuration.h> //micro-manager configuration
#include <Host.h>
#include <Host.cpp>
#include <math.h>

#include "defines.h"
#include "main.h"
#include "vision.h"
#include "tiffoperations.h"

using namespace std;
using namespace cv;
using namespace MM;

//CMMCore startup
CMMCore microCore; //it works!

int main(int argc, char* argv[])
{
	if (argc != 5)
	{
		printf("Invalid usage of program. Please run the program as following:\n");
		printf("Project.exe \"x:\\path\\reference.tif\" <magnification of reference.tif, e.g. 40, 20 (magnification used to create TIFF from scanner)> <camera magnification, e.g. 20, 10, 5 or 2.5> <magnification for initial match, e.g. 0.625, 1.25, 2.5, 5, 10 or 20>\n");
		return 0;
	}
	char* fileLocation = argv[1];
	double refMagnification = atof(argv[2]);
	// argument 3 can only have a few valid values, so check them
	if (refMagnification != 10.0 && refMagnification != 20.0 && refMagnification != 40.0)
	{
		printf("Invalid magnification for reference image used as parameter. Please run the program as following:\n");
		printf("Project.exe \"x:\\path\\reference.tif\" <magnification of reference.tif, e.g. 40, 20 (magnification used to create TIFF from scanner)> <camera magnification, e.g. 20, 10, 5 or 2.5> <magnification for initial match, e.g. 0.625, 1.25, 2.5, 5, 10 or 20>\n");
		return 0;
	}
	double templMagnification = atof(argv[3]);
	// argument 4 can only have a few valid values, so check them
	if (templMagnification != 10.0 && templMagnification != 5.0 && templMagnification != 2.5)
	{
		printf("Invalid magnification for camera used as parameter. Please run the program as following:\n");
		printf("Project.exe \"x:\\path\\reference.tif\" <magnification of reference.tif, e.g. 40, 20 (magnification used to create TIFF from scanner)> <camera magnification, e.g. 20, 10, 5 or 2.5> <magnification for initial match, e.g. 0.625, 1.25, 2.5, 5, 10 or 20>\n");
		return 0;
	}

	double matchMagnification = atof(argv[4]);
	if (matchMagnification != 0.625 && matchMagnification != 1.25 && matchMagnification != 2.5 && matchMagnification != 5.0 && matchMagnification != 10.0 && matchMagnification != 20.0)
	{
		printf("Invalid magnification for match used as parameter. Please run the program as following:\n");
		printf("Project.exe \"x:\\path\\reference.tif\" <magnification of reference.tif, e.g. 40, 20 (magnification used to create TIFF from scanner)> <camera magnification, e.g. 20, 10, 5 or 2.5> <magnification for initial match, e.g. 0.625, 1.25, 2.5, 5, 10 or 20>\n");
		return 0;
	}
	microCore.setTimeoutMs(10000);
	Mat rgbImage, camImg;
	double resizeScale;
	double currentXPos, currentYPos;
	double scanMatchPixSize, camMatchPixSize;
	int useDirectory;
	mouseData mouseEvent;
	Point matchLocation;
	Point mouseClick;

	//module for camera functions!
	HMODULE m_hLibHR3 = LoadLibrary( "hrfw.dll" );
	if (m_hLibHR3 == NULL)
	{
		cout << "failed to load library hrfw.dll, is it located in the directory of the executable?" << endl;
		cout << "hrfw.dll is required to run this software in order to use the zeiss axiocam HRc Rev. 3 camera, exiting program." << endl;
		exit(0);
	}
	mouseEvent.m_hLibHR3 = m_hLibHR3;
	//set the default pixel offset, are needed in order to get the correct area to display under microscope, mouseclicks are off by a little
	mouseEvent.pixOffSetX = 3;
	mouseEvent.pixOffSetY = 2;

	//function to close camera after shutdown
	void (WINAPI *p_McammClose)( long cameraindex );
	p_McammClose	= ( void(WINAPI*)(long) )GetProcAddress(m_hLibHR3, "_McammClose@4" );

	//give microCore time to start up.
	microCore.sleep(500);
	
	//connect microscope hardware
	connectHarware(m_hLibHR3, templMagnification);
	microCore.waitForSystem(); //wait for the hardwareconnection to be done, but it should be done at this point anyway
	//open the reference big tiff
	TIFF* tif = TIFFOpen(fileLocation, READONLY);
	
	//if opening succeeded then...
	if (tif) {
		useDirectory = log2(refMagnification/matchMagnification);
		//define the to be used directory of the main tiff, this has to be done dynamically as well, for now static
		int amountDirsInTiff = TIFFNumberOfDirectories(tif);
		if (useDirectory > amountDirsInTiff-1){
			printf("attempting to use a directory that is nonexistent in the loaded tiff, can't be done, please use a different match magnification.\n");
			return 0;
		}
		if (useDirectory <= 3){
			printf("Using a directory of a big tiff file that is equal or < 3 will cause severe slowdowns and/or excessive memory usage\nPlease use a different match magnification\n");
			return 0;
		}
		//...convert the image to a usable OpenCV Mat variable
		rgbImage = tiffToOpenCvMat(tif, useDirectory);
	}
	//or if it did not open successfully then...
	else {
		printf("Failed to open reference tif(f) file. Is the path and filename correct? Exiting.\n");
		exit(0);
	}
	//get the correct pixelsizes of the images, used for scaling purposes
	scanMatchPixSize = REFSCANMAG / matchMagnification * REFPIXSIZEMU40;
	camMatchPixSize = CAMSCANMAG / templMagnification * TEMPLPIXSIZEMU40;
	mouseEvent.scanMatchPixSize = scanMatchPixSize;
	mouseEvent.camMatchPixSize = camMatchPixSize;

	//acquire a template image from the camera.
	camImg = acquireImage(m_hLibHR3);

	//Downsample template functionalities
	//calculate the scaling needed according to magnifications being used
	resizeScale = camMatchPixSize / scanMatchPixSize;
	mouseEvent.resize = resizeScale;
	//... and then actually resize the image.
	resize(camImg, camImg, Size(), resizeScale, resizeScale, INTER_AREA);

	//now match the template with the reference image
	matchLocation = matchTemplRoutine(rgbImage, camImg);
	
	//view the result and used template!
	//for big images one would need a big screen to fit the result so there is a 
	//chance you don't see any result at all... with a smaller screen
	namedWindow( RESULT_WINDOW, CV_WINDOW_NORMAL );
	//bind the mouse to this window, we want to be able to navigate with the microscope based on mouseclicks in this image.
	setMouseCallback(RESULT_WINDOW, CallBackFunc, &mouseEvent);
	imshow( RESULT_WINDOW, rgbImage );

	namedWindow( TEMPLATE_WINDOW, CV_WINDOW_AUTOSIZE );
	imshow( TEMPLATE_WINDOW, camImg );
	
	//we want to know the center position of our found match.
	//matchLocation.x and matchLocation.y contain the upperleft corner of the found match.
	//add our template image cols / 2 to x and template image rows / 2 to y to find the center of the match.
	currentXPos = (matchLocation.x + (camImg.cols/2));
	currentYPos = (matchLocation.y + (camImg.rows/2));

	mouseEvent.offsetStageMatchX = microCore.getXPosition(MACXYSTAGE) / scanMatchPixSize - currentXPos;
	mouseEvent.offsetStageMatchY = microCore.getYPosition(MACXYSTAGE) / scanMatchPixSize - currentYPos;

	//while esc key not pressed or both template and reference window are still open, run forever.
	while(1){ //27 = ESC key in ascii
		int c = cvWaitKey(0);
		if (c == 27)
		{
			break; //if escape was pressed, break out of loop
		}
		if(!cvGetWindowHandle(RESULT_WINDOW) && !cvGetWindowHandle(TEMPLATE_WINDOW))
		{
			break; //if template and result (reference image) are closed, break out of loop
		}
	}

	(*p_McammClose)(0); //close camera
	FreeLibrary( m_hLibHR3 ); //free library
	cvDestroyAllWindows(); //destroy all opencv windows opened

	return(0);
}

//This function connects all hardware to the software, using micro-manager
int connectHarware(HMODULE m_hLibHR3, double useMag)
{
	//the following needs hardware connected to the computer... can only test it with
	//the microscope...
	//Initialize our serial port(s).
	microCore.loadDevice(ZEISSCOM, SERIALMANAGERLIB, COM_1);
    microCore.setProperty(ZEISSCOM, "BaudRate", BAUDRATE9600);
    microCore.setProperty(ZEISSCOM, "StopBits", "1");
	try{
		microCore.initializeDevice(ZEISSCOM);
	}
	catch (CMMError errorZeissCom){
		cout << "exception: " << errorZeissCom.getMsg() << " exiting." << endl;
		exit(1);
	}
	
	microCore.loadDevice(LUDLCOM, SERIALMANAGERLIB, COM_3);
	microCore.setProperty(LUDLCOM, "BaudRate", BAUDRATE9600);
	microCore.setProperty(LUDLCOM, "StopBits", "2");
	microCore.setProperty(LUDLCOM, "DelayBetweenCharsMs", "0");
	try{
		microCore.initializeDevice(LUDLCOM);
	}
	catch (CMMError errorLudlCom){
		cout << "exception: " << errorLudlCom.getMsg() << " exiting." << endl;
		exit(1);
	}
	//end of serial port(s) setup

	/*
	 * Load and initialize microscope modules
	 */
	microCore.loadDevice(AXIOPLAN2, ZEISSLIB, "ZeissScope");
	//CAN ONLY SET PORT IF CONNECTED!
	microCore.setProperty(AXIOPLAN2, "Port", ZEISSCOM); 
	try{
		microCore.initializeDevice(AXIOPLAN2);
	}
	catch (CMMError errorAxio){
		cout << "exception: " << errorAxio.getMsg() << " exiting." << endl;
		exit(1);
	}
	//load the objective turret
	microCore.loadDevice(OBJECTIVE_TURRET, ZEISSLIB, "ZeissObjectives"); //loads objectives turret
	try{
		microCore.initializeDevice(OBJECTIVE_TURRET); //initialize objectives turret
	}
	catch (CMMError errorObjective)
	{
		cout << "exception: " << errorObjective.getMsg() << " exiting." << endl;
		exit(1);
	}
	//switch the magnification to desired setting of launch
	if(useMag == 10.0)
	{
		microCore.setProperty(OBJECTIVE_TURRET, "State", OBJECTIVE10X); //switch to 10x objective
	}
	if(useMag == 5.0)
	{
		microCore.setProperty(OBJECTIVE_TURRET, "State", OBJECTIVE5X); //switch to 5x objective
	}
	else if(useMag == 2.5)
	{
		microCore.setProperty(OBJECTIVE_TURRET, "State", OBJECTIVE2DOT5X); //switch to 2.5x objective
	}
	//load the zstage
	microCore.loadDevice(ZDRIVE, ZEISSLIB, "Focus");
	try{
		microCore.initializeDevice(ZDRIVE);
	}
	catch (CMMError errorZdrive){
		cout << "exception: " << errorZdrive.getMsg() << " exiting." << endl;
		exit(1);
	}
	microCore.waitForDevice(OBJECTIVE_TURRET); //synchronization (wait for objectives to be done).
	/*
	 * End load Zeiss microscope
	 */


	/*
	 * load and initialize needed devices for the Ludl Mac5000 stage
	 */
	microCore.loadDevice(LUDLCONTROLLER, LUDLLIB, "LudlController");
	microCore.setProperty(LUDLCONTROLLER, "Port", LUDLCOM);
	try{
		microCore.initializeDevice(LUDLCONTROLLER);
	}
	catch (CMMError errorLudlControl){
		cout << "exception: " << errorLudlControl.getMsg() << " exiting." << endl;
		exit(1);
	}
	//load the xy stage
	microCore.loadDevice(MACXYSTAGE, LUDLLIB, "XYStage");
	try{
		microCore.initializeDevice(MACXYSTAGE);
	}
	catch (CMMError errorMacStage){
		cout << "exception: " << errorMacStage.getMsg() << " exiting." << endl;
		exit(1);
	}
	microCore.setOriginXY(MACXYSTAGE);
	//The stage uses a stepsize in microns, we are using a stepsize of 0.4 so set it to that
	microCore.setProperty(MACXYSTAGE, "StepSize", 0.39);
	microCore.waitForDevice(MACXYSTAGE);
	/*
	 *  End load Ludl Mac5000
	 */

	/*
	 * connect to the camera and set it up
	 */
	///////////////////////////////////////////////////////////////////////////
	// Initialize AxioCam HR Rev.3
	//declare functions from DLL to work with the camera (from zeiss documentation / example)
	long (WINAPI *p_NumCamsHR3)();
	long (WINAPI *p_McammInit)(long cameraindex);
	long (WINAPI *p_McammSetResolution)( long cameraindex, long Index );	
	long (WINAPI *p_McammSetBinning)( long cameraindex, long Binning );		
	long (WINAPI *p_McammSetBitsPerPixel)( long cameraindex, long bpp );
	long (WINAPI *p_McammSetFrameSize)( long cameraindex, RECT* pRect );
	long (WINAPI *p_McammSetExposure)( long cameraindex, long newVal );
	long (WINAPI *p_McammGetCurrentBitsPerPixel)(long cameraindex);		
	long (WINAPI *p_McammGetCurrentBinning)(long cameraindex, long* Binning);
	long (WINAPI *p_McammGetResolutionValues)( long cameraindex, long Index, long* pWidth, long* pHeight, eMcamScanMode* pMode );

	//load functions from DLL to work with the camera (from zeiss documentation / example)
	p_NumCamsHR3	= ( long(WINAPI*)(void) )GetProcAddress( m_hLibHR3, "_McamGetNumberofCameras@0" );
	p_McammInit		= ( long(WINAPI*)(long) )GetProcAddress( m_hLibHR3, "_McammInit@4");
	p_McammSetResolution		= ( long(WINAPI*)(long, long) )GetProcAddress( m_hLibHR3, "_McammSetResolution@8" );
	p_McammSetBinning	= (long (WINAPI*)(long,long))GetProcAddress( m_hLibHR3, "_McammSetBinning@8" );
	p_McammSetBitsPerPixel	= (long (WINAPI*)(long,long))GetProcAddress( m_hLibHR3, "_McammSetBitsPerPixel@8" );
	p_McammSetFrameSize	= (long (WINAPI*)(long,RECT*))GetProcAddress( m_hLibHR3, "_McammSetFrameSize@8" );
	p_McammSetExposure	= (long (WINAPI*)(long,long))GetProcAddress( m_hLibHR3, "_McammSetExposure@8" );
	p_McammGetCurrentBitsPerPixel		= (long (WINAPI*)(long))GetProcAddress( m_hLibHR3, "_McammGetCurrentBitsPerPixel@4" );
	p_McammGetCurrentBinning			= (long (WINAPI*)(long,long*))GetProcAddress( m_hLibHR3, "_McammGetCurrentBinning@8" );
	p_McammGetResolutionValues			= (long (WINAPI*)(long,long,long *,long *,eMcamScanMode *))GetProcAddress( m_hLibHR3, "_McammGetResolutionValues@20" );
	
	RECT newRect;
	long nNumHR3 = 0;

	if( m_hLibHR3 )	// library found
	{
		//now we have to check if ALL functions are correctly declared and found within this library.
		if( p_NumCamsHR3 && p_McammInit && p_McammSetResolution && p_McammSetBinning && p_McammSetBitsPerPixel && p_McammSetFrameSize
			&& p_McammSetExposure && p_McammGetCurrentBitsPerPixel && p_McammGetCurrentBinning && p_McammGetResolutionValues)// function pointers work
		{
			nNumHR3 = (*p_NumCamsHR3)();
			int err = (*p_McammInit)(0);
			if (err == NOERR)
			{
				//set the resolution
				int resErr = (*p_McammSetResolution)(0, 1);
				if (resErr == NOERR)
				{
					//cout<<"set resolution succes"<<endl;
				}
				else
				{
					cout<<"failed resolution settings"<<endl;
					cout<<resErr<<endl;
				}
				
				//set the correct amount of bits per pixel needed with this resolution setting (from zeiss documentation)
				int bppErr = (*p_McammSetBitsPerPixel)( 0, 42 );
				if(bppErr == NOERR)
				{
					long bppCur = (*p_McammGetCurrentBitsPerPixel)(0);
				}
				else
				{
					cout<<"failed to set bpp"<<endl;
					exit(0);
				}

				//set the correct binning mode that matches with this resolution setting (from zeiss documentation)
				int binErr = (*p_McammSetBinning)( 0, 1 );
				if(binErr == NOERR)
				{
					long binCur;
					(*p_McammGetCurrentBinning)(0, &binCur);
				}
				else
				{
					cout<<"failed to set bin"<<endl;
					exit(0);
				}
				//create a region of interest to create an image from later (from zeiss documentation / example)
				long sensorheight, sensorwidth;
				eMcamScanMode mode;
				(*p_McammGetResolutionValues)(0, 0, &sensorwidth, &sensorheight, &mode);
				SetRect(&newRect, 0, 0, sensorwidth, sensorheight);
				//set the exposure time, based on which magnification was used (from zeiss documentation)
				if(useMag == 10.0)
				{
					(*p_McammSetExposure)(0, 2500);
				}
				else if(useMag == 5.0)
				{
					(*p_McammSetExposure)(0, 4000);
				}
				else if(useMag == 2.5)
				{
					(*p_McammSetExposure)(0, 5500);
				}
				//give the settings some time to be processed.
				Sleep(400);
			}
			else
			{
				cout<<"Failed to initialize camera."<<endl;
				exit(0);
			}
		}
		else
		{
			cout << "One or more functions could not be found in hrfw.dll, are you sure this is the correct version of the file?" << endl;
			cout << "Please ensure hrfw.dll of the Zeiss AxioCam SDK 7.3 is located in the application folder, exiting..." << endl;
			exit(1);
		}
	}
	//dGetProps(MACXYSTAGE); //debug purposes only, use this if you want to get available properties for a device
	return 0;
}

//mouse callback function
void CallBackFunc(int event, int x, int y, int flags, void* userdata)
{
	double xPos, yPos;
	Mat temp;
	mouseData *p = (mouseData*) userdata;
	if  ( event == EVENT_LBUTTONDOWN )
	{
		//update the old x and y position with the current clicked pixel position
		//in case recalculation of the offset is needed
		p->oldX = x;
		p->oldY = y;

		//calculate the next stage micron position
		xPos = ((x - p->pixOffSetX) + p->offsetStageMatchX) * p->scanMatchPixSize;
		yPos = ((y - p->pixOffSetY) + p->offsetStageMatchY) * p->scanMatchPixSize;

		//move the stage to the position on which the user wants it
		microCore.setXYPosition(MACXYSTAGE, xPos, yPos);
		//wait for the stage to be done moving
		microCore.waitForDevice(MACXYSTAGE);
		//grab the current view from the microscope
		temp = acquireImage(p->m_hLibHR3);
		//resize it
		resize(temp, temp, Size(), p->resize, p->resize, INTER_AREA);
		//and display it!
		imshow( TEMPLATE_WINDOW, temp );
	}
	//below are the statements that could be used to process right and middle mouse buttons. The last one responds to mouse movements over a window.
	else if  ( event == EVENT_RBUTTONDOWN )
	{
		//if rightmousebutton was pressed, recalculate the pixel offset.
		p->pixOffSetX = p->pixOffSetX - (p->oldX - x);
		p->pixOffSetY = p->pixOffSetY - (p->oldY - y);
	}
	//else if  ( event == EVENT_MBUTTONDOWN )
	//{
	//cout << "Middle button of the mouse is clicked - position (" << x << ", " << y << ")" << endl;
	//}
	//else if ( event == EVENT_MOUSEMOVE )
	//{
		//cout << "Mouse move over the window - position (" << x << ", " << y << ")" << endl;
	//}
}

//the below function is only for debug purposes to retrieve a list of available properties 
//for a specific device
int dGetProps(char* label)
{
	vector<std::string> strvec = microCore.getDevicePropertyNames(label);
	string prop; //will contain property name, debug only.
	char *c; //will contain property name converted from prop, debug only.
	string val; //will contain property value, debug only.

	//Synchronization fix for the debug
	microCore.sleep(500);
	cout << endl;
	for(unsigned int i = 0; i<strvec.size(); i++)
	{
		prop = strvec[i];
		c = new char[prop.length()+1];
		strcpy(c, prop.c_str());
		val = microCore.getProperty(label, c);
		cout << "prop: " << c << " val: " << val << endl;
		delete [] c;
	}
	exit(0);
	return 0;
	//end debug
}

//microsoft visual c++ does not include log2() function... so let's write it here
double log2(double n)
{
	return log(n)/log(2.0);
}
